package com.ydlclass.factory.proxy;

import com.ydlclass.configuration.YrpcConfiguration;
import com.ydlclass.configuration.YrpcConfigurationHolder;
import com.ydlclass.handler.ChannelProvider;
import com.ydlclass.loadbalance.LoadBalancer;
import com.ydlclass.message.MessageType;
import com.ydlclass.message.MetaData;
import com.ydlclass.message.YrpcMessage;
import com.ydlclass.utils.GlobalRequestIdGenerator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * @author it楠老师
 * @createTime 2023-03-22
 */
@Slf4j
public class YrpcClientProxy<T> implements InvocationHandler {

    /**
     * 代理的接口
     */
    private final Class<?> interfaceClass;

    private Object object;

    private YrpcConfiguration yrpcConfiguration;

    public YrpcClientProxy(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }

    public YrpcClientProxy<T> getInstance() {
        return null;
    }

    public T get() {
        return (T) object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------invoke-----------");
        // 使用netty发送消息，根据接口封装消息
        long requestId = GlobalRequestIdGenerator.getId();
        YrpcMessage yrpcMessage = new YrpcMessage();
        yrpcMessage.setRequestId(requestId);
        yrpcMessage.setCompressTye(yrpcConfiguration.getCompress().getCode());
        yrpcMessage.setMessageType(MessageType.REQUEST.getValue());
        yrpcMessage.setSerializeType(yrpcConfiguration.getSerializer().getCode());

        // 封装调用接口的元数据
        MetaData metaData = new MetaData();
        metaData.setInterfaceName(interfaceClass.getName());
        Method calledMethod = interfaceClass.getMethod(method.getName(), method.getParameterTypes());
        metaData.setReturnType(calledMethod.getReturnType());
        metaData.setMethodName(calledMethod.getName());
        metaData.setParameterTypes(calledMethod.getParameterTypes());
        metaData.setParameterValue(args);
        yrpcMessage.setPayload(metaData);

        // todo 从注册中心抓取服务列表，当然如果有本地缓存则走本地缓存
        // 这里有个问题，1、新服务上线的感知问题，  2、服务掉线的感知问题
        List<InetSocketAddress> inetSocketAddresses = YrpcConfigurationHolder.get().getRegistry()
                .lookup(interfaceClass.getName());

        LoadBalancer loadBalance = YrpcConfigurationHolder.get().getLoadBalance();
        InetSocketAddress remoteAddress = loadBalance.selectServiceAddress(inetSocketAddresses, yrpcMessage);

        // todo 选取一个channel，目前该方法并不完善
        Channel channel = ChannelProvider.getInstance().selectChannel(remoteAddress);

        // 添加一个挂起的CompletableFuture
        // 1、如果一切正常处理，则在ProcessYrpcMessageHandler中调用complete方法
        // 2、如果发生异常，在监听器中处理
        Long start = System.currentTimeMillis();

        // 此处很关键，因为异步原因，我们需要
        // 1、在消息发送前创建一个CompletableFuture,并挂起
        // 2、channel接受到消息，调用complete方法
        // 3、调用结束使用get方法阻塞等待结果
        CompletableFuture<YrpcMessage> completableFuture = new CompletableFuture<>();
        PendingRequests.getInstance().put(requestId,completableFuture);
        if(log.isDebugEnabled()){
            log.debug("--> Message requestId[{}] is suspended.",requestId);
        }

        // 使用channel发送消息，如果发送失败记录异常到completableFuture
        channel.writeAndFlush(yrpcMessage).addListener((ChannelFutureListener) promise -> {
            if (!promise.isSuccess()) {
                Throwable cause = promise.cause();
                log.error("error", cause);
                completableFuture.completeExceptionally(promise.cause());
            }
        });

        // 阻塞获取结果，并将结果载荷返回作为返回值
        YrpcMessage message = completableFuture.get();
        if(log.isDebugEnabled()){
            log.debug("The method[{}]--requestId[{}]  call took {} milliseconds",
                    interfaceClass.getName()+method.getName(),
                    yrpcMessage.getRequestId(),
                    System.currentTimeMillis() - start);
        }
        return message.getPayload();
    }

    public Class<?> getInterfaceClass() {
        return interfaceClass;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public void setYrpcConfiguration(YrpcConfiguration yrpcConfiguration) {
        this.yrpcConfiguration = yrpcConfiguration;
    }
}
